﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;


namespace SkinableFormsLib
{
    public class LWSkinableForm : Form
    {
        private Timer _timer1;
        private Bitmap _bitmap;
        private System.ComponentModel.IContainer components = null;
        bool _hasControls = false;
        private Point _mdPoint;
        private bool _mdFlag = false;




        private bool _lwDragByControls = false;
        public bool LWDragByControls
        {
            set
            {

                if (!DesignMode)
                {
                    foreach (Control c in Controls)
                    {
                        if (value)
                        {
                            if (_lwDragByControls != value)
                            {
                                c.MouseDown += new MouseEventHandler(Control_MouseDown);
                                c.MouseUp += new MouseEventHandler(Control_MouseUp);
                                c.MouseMove += new MouseEventHandler(Control_MouseMove);
                            }
                        }
                        else
                        {
                            if (_lwDragByControls != value)
                            {
                                c.MouseDown -= new MouseEventHandler(Control_MouseDown);
                                c.MouseUp -= new MouseEventHandler(Control_MouseUp);
                                c.MouseMove -= new MouseEventHandler(Control_MouseMove);
                            }
                        }
                    }
                }
                _lwDragByControls = value;
            }
            get
            {
                return _lwDragByControls;
            }
        }





        private bool _lwForceRedraw = false;
        public bool LWForceRedraw
        {
            set
            {
                _lwForceRedraw = value;
                if (!DesignMode)
                {
                    _timer1.Enabled = _lwForceRedraw;
                }
            }
            get
            {
                return _lwForceRedraw;
            }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public LWSkinableForm()
        {
            // This form should not have a border or else Windows will clip it.
            FormBorderStyle = FormBorderStyle.None;
            InitializeComponent();
        }


        /// <summary>
        /// Used only while the form is activated (in focus)
        /// </summary>
        /// <see cref="P:WindowsFormsApplication1.PerPixelAlphaForm.WndProc"/>
        private int _lwTimerIntervalFast = 10;
        public int LWTimerIntervalFast
        {
            set
            {
                _lwTimerIntervalFast = value;
            }
            get
            {
                return _lwTimerIntervalFast;
            }
        }

        /// <summary>
        /// Most used timer during the lifetime of the form
        /// Not used while dragging or while the form is activated
        /// </summary>
        private int _lwTimerIntervalMedium = 100;
        public int LWTimerIntervalMedium
        {
            set
            {
                _lwTimerIntervalMedium = value;
            }
            get
            {
                return _lwTimerIntervalMedium;
            }
        }

        /// <summary>
        /// used only while the form is being dragged
        /// 
        /// </summary>
        /// <see cref="P:WindowsFormsApplication1.PerPixelAlphaForm.WndProc"/>
        private int _lwTimerIntervalSlow = 10000;
        public int LWTimerIntervalSlow
        {
            set
            {
                _lwTimerIntervalSlow = value;
            }
            get
            {
                return _lwTimerIntervalSlow;
            }
        }

        /// <summary>
        /// Overall form opacity (byte: 0->255)
        /// </summary>
        private byte _lwOpacity = 255;
        public Byte LWOpacity
        {
            set
            {

                _lwOpacity = value;
                if (!DesignMode)
                {
                    if (_bitmap != null)
                    {
                        SetBitmap(_bitmap, _lwOpacity);
                    }
                }
            }

            get
            {
                return _lwOpacity;
            }
        }

        /// <summary>
        /// Overrides WndProc to allow visual dragging
        /// </summary>
        /// <param name="m"></param>
        protected override void WndProc(ref Message m)
        {
            if (!DesignMode)
            {
                //http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1955240&SiteID=1
                if (m.Msg == Win32.WM_NCMOUSEMOVE)
                {
                    _timer1.Interval = _lwTimerIntervalFast;
                }
                if (m.Msg == Win32.WM_NCLBUTTONDBLCLK)
                    return;
                if (m.Msg == Win32.WM_NCHITTEST)
                {
                    _timer1.Interval = _lwTimerIntervalSlow;
                    m.Result = (IntPtr)2;	// HTCLIENT
                    return;
                }
            }
            base.WndProc(ref m);

        }


        public override Image BackgroundImage
        {
            get
            {
                return base.BackgroundImage;
            }
            set
            {
                base.BackgroundImage = value;
                this.Size = BackgroundImage.Size;
            }
        }






        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }



        /// <para>Changes the current bitmap.</para>
        public void SetBitmap(Bitmap bitmap)
        {
            SetBitmap(bitmap, 255);
        }



        /// <para>Changes the current bitmap with a custom opacity level.  Here is where all happens!</para>
        public void SetBitmap(Bitmap bitmap, byte opacity)
        {
            if (DesignMode)
                return;
            if (bitmap == null)
                return;
            if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
                throw new ApplicationException("The bitmap must be 32ppp with alpha-channel.");

            _bitmap = (Bitmap)bitmap.Clone();
            _lwOpacity = opacity;

            // The idea of this is very simple,
            // 1. Create a compatible DC with screen;
            // 2. Select the bitmap with 32bpp with alpha-channel in the compatible DC;
            // 3. Call the UpdateLayeredWindow.
            this.Refresh();
            IntPtr screenDc = Win32.GetDC(IntPtr.Zero);
            IntPtr memDc = Win32.CreateCompatibleDC(screenDc);
            IntPtr hBitmap = IntPtr.Zero;
            IntPtr oldBitmap = IntPtr.Zero;

            try
            {

                /***********************************/
                /**********  Draw controls *********/
                /***********************************/
                Bitmap temp_bmp = new Bitmap(bitmap);
                Bitmap b = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppArgb);
                Graphics g = Graphics.FromImage(temp_bmp);

                foreach (Control ctrl in this.Controls)
                {
                    if (ctrl.Visible)
                    {

                        if (ctrl is ILWControl)
                        {

                            ctrl.DrawToBitmap(new Bitmap(1, 1), ctrl.Bounds);
                            if (((ILWControl)ctrl).LWBitmap!=null)
                            g.DrawImage(((ILWControl)ctrl).LWBitmap, ctrl.Location);
                        }
                        else
                        {
                            ctrl.DrawToBitmap(temp_bmp, ctrl.Bounds);
                        }
                    }
                }

                g.Flush();


                hBitmap = temp_bmp.GetHbitmap(Color.FromArgb(0));
                //hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));  // grab a GDI handle from this GDI+ bitmap
                oldBitmap = Win32.SelectObject(memDc, hBitmap);

                Win32.Size size = new Win32.Size(bitmap.Width, bitmap.Height);
                Win32.Point pointSource = new Win32.Point(0, 0);
                Win32.Point topPos = new Win32.Point(Left, Top);
                this.Location = new Point(topPos.x, topPos.y);
                this.Size = new Size(size.cx, size.cy);

                Win32.BLENDFUNCTION blend = new Win32.BLENDFUNCTION();
                blend.BlendOp = Win32.AC_SRC_OVER;
                blend.BlendFlags = 0;
                blend.SourceConstantAlpha = opacity;
                blend.AlphaFormat = Win32.AC_SRC_ALPHA;

                Win32.UpdateLayeredWindow(Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, Win32.ULW_ALPHA);
                temp_bmp.Dispose();


            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
            finally
            {
                Win32.ReleaseDC(IntPtr.Zero, screenDc);
                if (hBitmap != IntPtr.Zero)
                {
                    Win32.SelectObject(memDc, oldBitmap);
                    //Windows.DeleteObject(hBitmap); // The documentation says that we have to use the Windows.DeleteObject... but since there is no such method I use the normal DeleteObject from Win32 GDI and it's working fine without any resource leak.
                    Win32.DeleteObject(hBitmap);
                }
                Win32.DeleteDC(memDc);
                bitmap.Dispose();

            }
        }


        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                if (!this.DesignMode)//!important! - will crash designer in VS 2008 (maybe others too) if not used
                {
                    cp.ExStyle |= 0x00080000; // This form has to have the WS_EX_LAYERED extended style
                }
                return cp;
            }
        }

        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this._timer1 = new System.Windows.Forms.Timer(this.components);
            this.SuspendLayout();
            // 
            // _timer1
            // 
            this._timer1.Tick += new System.EventHandler(this.timer1_Tick);
            // 
            // PerPixelAlphaForm
            // 
            this.ClientSize = new System.Drawing.Size(289, 273);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
            this.Name = "PerPixelAlphaForm";
            this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
            this.Deactivate += new System.EventHandler(this.PerPixelAlphaForm_Deactivate);
            this.Load += new System.EventHandler(this.PerPixelAlphaForm_Load);
            this.Activated += new System.EventHandler(this.PerPixelAlphaForm_Activated);
            this.ResumeLayout(false);
        }


        private void timer1_Tick(object sender, EventArgs e)
        {
            //Console.WriteLine("tick");
            SetBitmap(_bitmap, _lwOpacity);
        }



        private void PerPixelAlphaForm_Load(object sender, EventArgs e)
        {

            if (Controls.Count > 0)
            {
                _hasControls = true;
            }
            if (!DesignMode)
            {
                if (BackgroundImage != null)
                {
                    Bitmap b = (Bitmap)BackgroundImage.Clone();
                    SetBitmap(b, _lwOpacity);
                    //BackgroundImage = null;//remove form background image
                }
                _timer1.Enabled = _lwForceRedraw || _hasControls;//start redraw timer only if LWForceRedraw or at least one control is present
            }
        }

        private void PerPixelAlphaForm_Activated(object sender, EventArgs e)
        {
            _timer1.Interval = _lwTimerIntervalFast;
        }
        private void PerPixelAlphaForm_Deactivate(object sender, EventArgs e)
        {
            _timer1.Interval = _lwTimerIntervalMedium;
        }



        protected override void OnControlRemoved(ControlEventArgs e)
        {
            base.OnControlRemoved(e);
            if (_lwDragByControls)
            {
                try
                {
                    e.Control.MouseDown -= new MouseEventHandler(Control_MouseDown);
                    e.Control.MouseUp -= new MouseEventHandler(Control_MouseUp);
                    e.Control.MouseMove -= new MouseEventHandler(Control_MouseMove);
                    e.Control.MouseDown -= new MouseEventHandler(Control_MouseDown_Timer);
                    e.Control.MouseUp -= new MouseEventHandler(Control_MouseUp_Timer);


                }
                finally
                {

                }
            }
        }

        protected override void OnControlAdded(ControlEventArgs e)
        {
            base.OnControlAdded(e);
            if (_lwDragByControls)
            {
                e.Control.MouseDown += new MouseEventHandler(Control_MouseDown);
                e.Control.MouseUp += new MouseEventHandler(Control_MouseUp);
                e.Control.MouseMove += new MouseEventHandler(Control_MouseMove);
            }

            e.Control.MouseDown += new MouseEventHandler(Control_MouseDown_Timer);
            e.Control.MouseUp += new MouseEventHandler(Control_MouseUp_Timer);
        }

        void Control_MouseUp_Timer(object sender, MouseEventArgs e)
        {
            //_timer1.Interval = _lwTimerIntervalMedium;
        }

        void Control_MouseDown_Timer(object sender, MouseEventArgs e)
        {
            _timer1.Interval = _lwTimerIntervalFast;
        }
        
        void Control_MouseMove(object sender, MouseEventArgs e)
        {

            if (_mdFlag)
            {
                Point nl = Location;
                nl.Offset(e.X - _mdPoint.X, e.Y - _mdPoint.Y);
                this.Location = nl;
            }
        }

        void Control_MouseUp(object sender, MouseEventArgs e)
        {

            _mdFlag = false;
            _timer1.Interval = _lwTimerIntervalFast;
        }


        void Control_MouseDown(object sender, MouseEventArgs e)
        {
            _timer1.Interval = _lwTimerIntervalSlow;
            _mdFlag = true;
            _mdPoint = e.Location;
            
            


        }

    }
}
